/// <summary>
/// Copyright 2010 Ivo Limmen (ivo@limmen.org)
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
/// 
/// http://www.apache.org/licenses/LICENSE-2.0
/// 
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
/// </summary>
using System;
using System.Configuration;
using Services.Configuration.Api;
using System.Net;
using System.Collections.Generic;

namespace Lib.Configuration
{
	class CacheItem
	{
		public DateTime Retrieved { get; set; }

		public string Id { get; set; }

		public string Value { get; set; }

		public CacheItem(string sectionName, string keyName) : this(sectionName, keyName, null)
		{
		}

		public CacheItem(string sectionName, string keyName, string value)
		{
			this.Id = sectionName + ":" + keyName;
			this.Value = value;
			this.Retrieved = DateTime.Now;
		}

		public override int GetHashCode()
		{
			return 37 * this.Id.GetHashCode();
		}

		public override bool Equals(object obj)
		{
			if (obj != null && obj.GetType().Equals(typeof(CacheItem)))
			{
				CacheItem other = (CacheItem)obj;
				
				return this.Id != null && this.Id.Equals(other.Id);
			}
			
			return false;
		}

		public bool IsOld {
			get { return DateTime.Now.Subtract(this.Retrieved).TotalMinutes > 10; }
		}

		public bool IsEmpty {
			get { return this.Value == null; }
		}

		public void Refresh(string value)
		{
			this.Value = value;
			this.Retrieved = DateTime.Now;
		}
	}

	/// <summary>
	/// Utility class for retrieving configuration settings from an IConfigService.
	/// </summary>
	public class ConfigUtil
	{
		private static ConfigUtil current = new ConfigUtil();

		private List<CacheItem> cache = new List<CacheItem>();

		private string url = null;

		private string appName = null;

		private string hostName = null;

		private bool initialized = false;

		private ConfigUtil()
		{
		}

		/// <summary>
		/// Initialize the ConfigUtil. This method can only be called once. Second time will throw an exception.
		/// </summary>
		/// <param name="url">
		/// String containing the URL to the remote IConfigService (i.e. tcp://localhost:1519/ConfigService).
		/// If <code>null</code> will default to the configuration key: "RemoteConfigurationService".
		/// </param>
		/// <param name="appName">
		/// Name of the application that requires a configuration setting. When <code>null</code> will default
		/// to the configuration key: "ApplicationName".
		/// </param>
		public static void Initialize(string url, string appName)
		{
			if (!current.initialized)
			{
				if (url != null)
				{
					current.url = url;
				}

				else
				{
					current.url = ConfigurationManager.AppSettings["RemoteConfigurationService"];
				}
				
				if (appName != null)
				{
					current.appName = appName;
				}

				else
				{
					current.appName = ConfigurationManager.AppSettings["ApplicationName"];
				}

				// retrieve the name of this machine
				current.hostName = Dns.GetHostName();
				current.initialized = true;
			}
			else
			{
				throw new Exception("ConfigUtil is allready initialized!");
			}
		}

		private static ConfigUtil GetInstance()
		{
			if (!current.initialized)
			{
				Initialize(null, null);
			}

			return current;
		}

		private string GetSettingFromService(string sectionName, string keyName)
		{
			IConfigService configService = (IConfigService)Activator.GetObject(typeof(IConfigService), this.url);

			return configService.GetSetting(this.appName, this.hostName, sectionName, keyName);
		}

		/// <summary>
		/// Retrieve a setting from a remote IConfigService.
		/// </summary>
		/// <param name="sectionName">
		/// Section the key is stored in.
		/// </param>
		/// <param name="keyName">
		/// Key of the setting to retrieve.
		/// </param>
		/// <returns>
		/// Returns a string containing the value that was requested.
		/// </returns>
		public static string GetSetting(string sectionName, string keyName)
		{
			return GetInstance().RetrieveSetting(sectionName, keyName);
		}

		/// <summary>
		/// Returns a setting and converts it to a Decimal.
		/// </summary>
		public static decimal? GetSettingAsDecimal(string sectionName, string keyName)
		{
			string value = GetInstance().RetrieveSetting(sectionName, keyName);
			
			if (value == null)
			{
				return null;
			}
			
			return Decimal.Parse(value);
		}

		private string RetrieveSetting(string sectionName, string keyName)
		{
			CacheItem item = new CacheItem(sectionName, keyName);
			
			if (this.cache.Contains(item))
			{
				item = this.cache[this.cache.IndexOf(item)];
				
				if (item.IsOld)
				{
					item.Refresh(GetSettingFromService(sectionName, keyName));
				}
			}

			else
			{
				item = new CacheItem(sectionName, keyName, GetSettingFromService(sectionName, keyName));
				
				if (!item.IsEmpty)
				{
					this.cache.Add(item);
				}
			}
			
			return item.Value;
		}
	}
}
